/*
 * Copyright (C) 2012 by Zhang Li <RichSelian at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "nw-conninfo.h"

nw_conninfo_prioqueue_t* nw_conninfo_prioqueue_create(size_t capacity) {
    nw_conninfo_prioqueue_t* queue;
    size_t i;

    if((queue = malloc(sizeof(nw_conninfo_prioqueue_t) + sizeof(nw_conninfo_node_t) * capacity)) != NULL) {
        queue->m_capacity = capacity;
        queue->m_size = 0;
        queue->m_freenode = NULL;
        queue->m_queue.m_next = &queue->m_queue;
        queue->m_queue.m_prev = &queue->m_queue;

        for(i = 0; i < capacity; i++) {
            queue->m_poolnodes[i].m_next = queue->m_freenode;
            queue->m_freenode = &queue->m_poolnodes[i];
        }
    }
    return queue;
}

nw_conninfo_node_t* nw_conninfo_prioqueue_append(nw_conninfo_prioqueue_t* queue, int connsock, struct sockaddr_in* connaddr) {
    nw_conninfo_node_t* new_node;
    time_t now = time(0);

    if(queue->m_size == queue->m_capacity) { /* no freenodes left, force removing oldest nodes */
        nw_conninfo_prioqueue_remove_timeout(queue, now - queue->m_queue.m_prev->m_active_time);
    }
    new_node = queue->m_freenode;
    queue->m_freenode = new_node->m_next;

    new_node->m_connsock = connsock;
    new_node->m_connaddr = connaddr[0];
    new_node->m_active_time = now;
    new_node->m_handle_close = 1;

    new_node->m_next = queue->m_queue.m_next;
    new_node->m_prev = &queue->m_queue;
    new_node->m_next->m_prev = new_node;
    new_node->m_prev->m_next = new_node;
    queue->m_size += 1;
    return new_node;
}

nw_conninfo_node_t* nw_conninfo_prioqueue_update(nw_conninfo_prioqueue_t* queue, nw_conninfo_node_t* node) {
    time_t now = time(0);

    node->m_next->m_prev = node->m_prev;
    node->m_prev->m_next = node->m_next;
    node->m_active_time = now;
    node->m_next = queue->m_queue.m_next;
    node->m_prev = &queue->m_queue;
    node->m_next->m_prev = node;
    node->m_prev->m_next = node;
    return node;
}

void nw_conninfo_prioqueue_remove_timeout(nw_conninfo_prioqueue_t* queue, int second) {
    nw_conninfo_node_t* del_node;
    time_t now = time(0);

    while(queue->m_size > 0 && now - queue->m_queue.m_prev->m_active_time >= second) {
        del_node = queue->m_queue.m_prev;
        del_node->m_next->m_prev = del_node->m_prev;
        del_node->m_prev->m_next = del_node->m_next;

        if(del_node->m_handle_close) {
            shutdown(del_node->m_connsock, SHUT_RD);
            close(del_node->m_connsock);
            if(errno != EBADF) {
                fprintf(stderr, "nowhttpd-log: connection from '%s'(fd = %d) closed due to timeout.\n",
                        inet_ntoa(del_node->m_connaddr.sin_addr),
                        del_node->m_connsock);
            }
        }
        del_node->m_next = queue->m_freenode;
        queue->m_freenode = del_node;
        queue->m_size -= 1;
    }
    return;
}

void nw_conninfo_prioqueue_destroy(nw_conninfo_prioqueue_t* queue) {
    nw_conninfo_prioqueue_remove_timeout(queue, 0);
    free(queue);
    return;
}
